#pragma once
#include <thread>
#include "Shader.h"
#include "PbrMaterial.h"
#include "Light.h"
#include "Transform.h"

class Entity {
public:
	/** @brief any child must has a specified parent */
	Entity(Entity* parent = nullptr): m_parent(parent) {
		if (m_parent)
			m_parent->addChild(this);
	}

	/**
	 *@note use `protected` modifier to avoid creating objects on the stack, so you must use `new` to create object!
	 *@note use `virtual` modifier to make sure derived class's destructor be called, otherwise, when `delete` base pointer, only base destructor be called!
	 */
	virtual ~Entity() {
		for (auto entity : m_children)
			delete entity;
	}

	void install(const CString& shaderName);

	template <typename T>
	void installExtraVABO(const char* attribName, const CVector<T>& attribData) { m_shader.prepareVABO(attribName, attribData); }

	void setMeshName(const CString& meshName) { m_meshName = meshName; }
	const CString& meshName() { return m_meshName; }

	std::thread::id bornThread() { return m_bornThread; }// just for debug

	void setPrimitiveType(PrimitiveType primitiveType) { m_primitiveType = primitiveType; }

	void setVertices(const CVector<P>& vertices, float y) { m_2dVertices = vertices; m_2dVertexY = y; }
	void setVertices(const CVector<Vec3>& vertices) { m_vertices = vertices; }
	void setInstancedVertices(const CVector<P>& vertices, float y = 0) { m_instanced2dVertices = vertices; m_instanced2dVertexY = y; }
	void setInstancedVertices(const CVector<Vec3>& vertices) { m_instancedVertices = vertices; }
	void setIndices(const CVector<GLuint>& indices) { m_indices = indices; }
	void setNormals(const CVector<Vec3>& normals) { m_normals = normals; }
	void setTexcoords(const CVector<Vec2>& texcoords) { m_texcoords = texcoords; }

	void setPureColor(const CColor& color) { m_pureColor = color; }
	void setMaterialColor(const CColor& color) { addUniform("material.baseColor", color); }

	void setFadeOutEndZ(float endZ, int zOffsetPx) { m_fadeOutEndZ = endZ; m_fadeOutZOffsetPx = zOffsetPx; }
	void disableFadeOutEdge() { m_fadeOutEdgeEnabled = false; }

	void addUniform(const CString& name, const std::any& value) { m_standaloneUniformKVs.insert(name, value); }

	/**@example addUniform("zStart", 0.1f, "zEnd", 96, "edgeW", 33);*/
	void addUniform(const std::string&) {}// a must: stop condition
	template<typename... Args>
	void addUniform(const std::string& name, const std::any& value, const std::string& nextName, Args... args) {
		addUniform(name, value);
		if (sizeof...(args) > 0)
			addUniform(nextName, args...);
	}

	/**@example addStruct("material", MakePair("baseColor", myBaseColor), MakePair(k2,v2), ...);*/
	void addUniformStruct(const std::string&) {}// a must: stop condition
	template<typename T, typename... Args>
	void addUniformStruct(const std::string& structName, const Pair<const char*, T>& field, Args... args) {
		addUniform(structName + "." + field.key, field.value);
		if (sizeof...(args) > 0)
			addUniformStruct(structName, args...);
	}

	template<typename T>
	void addUniformArray(const CString& name, const CVector<T>& values) {
		CVector<std::any> list;
		for (auto& t : values) list << t;
		m_standaloneUniformArrayKVs.insert(name, list);
	}

	void setSampler2D(const Sampler2DArg& sampler2DArg) { m_sampler2DArg = sampler2DArg; }

	void setMaterial(const PbrMaterial& material) { m_material = material; }
	PbrMaterial& materialRef() { return m_material; }

	void setLightEnabled(bool enabled) { m_lightEnabled = enabled; }
	void setCullFaceEnabled(bool enabled) { m_cullFaceEnabled = enabled; }

	void setTransform(const Transform& transform) { m_transform = transform; }
	Transform& transformRef() { return m_transform; }

	void setScale(float scale) { setScale(scale, scale, scale); }
	void setScale(float sx, float sy, float sz) { m_transform.setGlobalScale(sx, sy, sz); }
	const Vec3& scale() { return m_transform.globalScale(); }
	void setPosition(const Vec3& position) { m_transform.setGlobalPos(position); }
	const Vec3& position() { return m_transform.globalPos(); }
	void setRotationY(float rotationY, const Vec3& aroundCenter = Vec3(0.0)) { m_transform.setGlobalRotY(rotationY, aroundCenter); }

	void setMeshProperty(int meshIndex, const CString& meshName) { m_meshProperty = MakePair(meshIndex, meshName); }
	const Pair<int, CString>& meshProperty() const { return m_meshProperty; }

	Entity* child(int meshIndex) { return m_children[meshIndex]; }
	const CVector<Entity*>& children() { return m_children; }

	void render() {
		if (m_children.empty()) {
			update();
		} else {
			for (auto child : m_children) {
				child->transformRef().setParentTransform(&m_transform);
				child->update();
			}
		}
	}

	virtual void updateState() {}
	virtual EntityType type() const = 0;
	virtual RenderOrder renderOrder() const = 0;

private:
	void update();

	void applyMVPMatrix();

	void set2dVertexY();

	void setPreGLState();

	void setPostGLState();

	void applyMaterial();

	void applyLight(Light::Type type);

	void applyFadeOut();

	void setStandaloneUniforms();

	void addChild(Entity* entity) { m_children << entity; }

	std::thread::id m_bornThread = std::this_thread::get_id();

	PrimitiveType m_primitiveType = PrimitiveType::TRIANGLES;

	Shader m_shader;
	PbrMaterial m_material;
	Transform m_transform;

	CString m_meshName;

	Uniform m_uniform;

	CMap<CString, std::any> m_standaloneUniformKVs;
	CMap<CString, CVector<std::any>> m_standaloneUniformArrayKVs;

	Sampler2DArg m_sampler2DArg;

	CVector<Vec3> m_vertices, m_instancedVertices;
	CVector<P> m_2dVertices, m_instanced2dVertices;
	float m_2dVertexY, m_instanced2dVertexY;
	CVector<GLuint> m_indices;

	CVector<Vec3> m_normals;
	CVector<Vec2> m_texcoords;

	Pair<int, CString> m_meshProperty;// (meshIndex, meshName)

	CVector<Entity*> m_children;

	CColor m_pureColor;

	bool m_fadeOutEdgeEnabled = true;
	float m_fadeOutEndZ = -1;
	int m_fadeOutZOffsetPx;

	Entity* m_parent = nullptr;

	bool m_lightEnabled = false, m_cullFaceEnabled = true;
	bool m_isLightDirty = true;
};